package org.osite.xml;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.osite.util.ToolKit;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

public class DefaultEntityHandler<T> implements EntityHandler<T> {

    private static Pattern SETTER_PAT = Pattern.compile("set([A-Z]\\w+)");
    private static Pattern ADDER_PAT = Pattern.compile("add([A-Z]\\w+)");

    private final Constructor<T> ctor;
    private final ArgumentsHelper param;

    private final IdentityHashMap<MemberHandler<T>, Boolean> onceHandlers =
            ToolKit.newIdentityHashMap();
    private final HashMap<String, AttributeHandler<T>> attrHandlers =
            new HashMap<String, AttributeHandler<T>>();
    private final HashMap<String, MemberHandler<T>> byNameHandlers =
            ToolKit.newHashMap();
    private final HashMap<Class<?>, MemberHandler<T>> byTypeHandlers =
            ToolKit.newHashMap();

    private final String tagName;
    private final Class<T> type;

    public static <T> DefaultEntityHandler<T> newInstance(Class<T> type,
            ConvertorProvider provider) throws ParseSetupException {
        return new DefaultEntityHandler<T>(type, provider);
    }

    public DefaultEntityHandler(Class<T> type, ConvertorProvider provider)
            throws ParseSetupException {
        XmlEntity entity = type.getAnnotation(XmlEntity.class);
        this.type = type;
        this.tagName = (entity == null) ? type.getSimpleName() : entity.value();
        ctor = ParseUtil.checkAccessibility(getEntityConstructor(type));
        param = new ArgumentsHelper(ctor, provider);
        initializeHandlers(type, provider);
    }

    @SuppressWarnings("unchecked")
    private Constructor<T> getEntityConstructor(Class<T> type)
            throws ParseSetupException {
        Constructor<T> result = null;
        for (Constructor<?> c : type.getDeclaredConstructors()) {
            if (c.getAnnotation(XmlConstructor.class) != null) {
                if (result != null) {
                    throw new ParseSetupException(
                            "two or more constructors marked with "
                            + XmlConstructor.class);
                }
                result = (Constructor<T>) c;
            }
        }
        try {
            return ((result == null) ? type.getDeclaredConstructor() : result);
        } catch (SecurityException e) {
            throw new ParseSetupException("invalid default ctor: " + type, e);
        } catch (NoSuchMethodException e) {
            throw new ParseSetupException("invalid default ctor: " + type, e);
        }
    }

    private void initializeHandlers(Class<T> type, ConvertorProvider provider)
            throws ParseSetupException {
        Class<?> current = type;
        while (current != Object.class) {
            HashSet<String> namespace = new HashSet<String>();
            HashSet<Class<?>> typespace = ToolKit.newHashSet();;
            for (Method method : current.getDeclaredMethods()) {
                int mod = method.getModifiers();
                if (Modifier.isStatic(mod) || !Modifier.isPublic(mod)) {
                    continue;
                }
                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }
                prepareAttributeHandlers(namespace, method, provider);
                prepareMemberHandler(namespace, typespace, method, provider);
            }
            current = current.getSuperclass();
        }
    }

    private void prepareMemberHandler(
            HashSet<String> namespace, HashSet<Class<?>> typespace,
            Method method, ConvertorProvider provider)
            throws ParseSetupException {
        if (Modifier.isStatic(method.getModifiers())) {
            return;
        }
        XmlMember membered = method.getAnnotation(XmlMember.class);
        if (membered == null) return;
        Class<?> types[] = method.getParameterTypes();
        if (types.length == 0) {
            throw new ParseSetupException("invalid handler: " + method);
        }

        String name = membered.value();
        boolean executeOnce = membered.executeOnce();
        if (name.isEmpty()) {
            executeOnce = true;
            Matcher matcher = SETTER_PAT.matcher(method.getName());
            if (!matcher.matches()) {
                matcher = ADDER_PAT.matcher(method.getName());
                executeOnce = false;
            }
            name = matcher.matches() ? matcher.group(1) : null;
        }
        if (name == null) {
            throw new ParseSetupException(
                    "cannot get member name: " + method);
        }
        MemberHandler<T> handler = buildMemberHandler(method, membered);
        if (handler == null) {
            handler = new DefaultMemberHandler<T>(method, provider);
        }
        if (executeOnce) onceHandlers.put(handler, Boolean.TRUE);

        if (membered.identifyByType()) {
            putByTypeHandler(types[0], handler, typespace);
        } else {
            putByNameHandler(name, handler, namespace);
        }
    }

    private void prepareAttributeHandlers(HashSet<String> namespace,
            Method method, ConvertorProvider provider)
            throws ParseSetupException {
        if (Modifier.isStatic(method.getModifiers())) {
            return;
        }

        XmlAttribute attred = method.getAnnotation(XmlAttribute.class);
        if (attred != null) {
            String name = attred.value();
            if (name.isEmpty()) {
                Matcher matcher = SETTER_PAT.matcher(method.getName());
                name = matcher.matches()
                        ? ParseUtil.toXmlConvention(matcher.group(1))
                        : null;
            }
            if (name == null) {
                throw new ParseSetupException(
                        "cannot get attribute name: " + method);
            }
            AttributeHandler<T> handler =
                    buildAttributeHandler(method, attred);
            if (handler == null) {
                handler = new DefaultAttributeHandler<T>(method, provider);
            }
            putAttributeHandler(name, handler, namespace);
        }
    }

    private void putByTypeHandler(Class<?> type,
            MemberHandler<T> handler, HashSet<Class<?>> namespace)
            throws ParseSetupException{
        if (!namespace.add(type)) {
            throw new ParseSetupException("type \"" + type
                    + "\" is duplicated");
        }
        if (!byTypeHandlers.containsKey(type)) {
            byTypeHandlers.put(type, handler);
        }
    }

    private void putByNameHandler(
            String name, MemberHandler<T> handler, HashSet<String> namespace)
            throws ParseSetupException {
        if (!namespace.add(name)) {
            throw new ParseSetupException("member \"" + name
                    + "\" is duplicated");
        }
        if (!byNameHandlers.containsKey(name)) {
            byNameHandlers.put(name, handler);
        }
    }

    private void putAttributeHandler(
            String name, AttributeHandler<T> handler,
            HashSet<String> namespace) throws ParseSetupException {
        if (!namespace.add(name)) {
            throw new ParseSetupException("attribute \"" + name
                    + "\" is duplicated");
        }
        if (!attrHandlers.containsKey(name)) {
            attrHandlers.put(name, handler);
        }
    }

    private MemberHandler<T> buildMemberHandler(
            Method setter, XmlMember membered) throws ParseSetupException {
        try {
            if (!membered.handler().isInterface()) {
                @SuppressWarnings("unchecked")
                Class<? extends MemberHandler<T>> type = (
                        Class<? extends MemberHandler<T>>) membered.handler();
                return ParseUtil.newInstance(type);
            }
            return null;
        } catch (PasreReflectionException e) {
            throw new ParseSetupException(e);
        }
    }

    private AttributeHandler<T> buildAttributeHandler(Method setter,
            XmlAttribute attred) throws ParseSetupException {
        try {
            if (!attred.handler().isInterface()) {
                @SuppressWarnings("unchecked")
                Class<? extends AttributeHandler<T>> type = (
                        Class<? extends AttributeHandler<T>>) attred.handler();
                return ParseUtil.newInstance(type);
            }
            if (!attred.convertor().isInterface()) {
                return new DefaultAttributeHandler<T>(setter, ParseUtil
                        .newInstance(attred.convertor()));
            }
            return null;
        } catch (PasreReflectionException e) {
            throw new ParseSetupException(e);
        }
    }

    public T create(Element element, ParseContext context)
            throws XmlParseException {
        T entity;
        try {
            entity = ParseUtil.newInstance(
                    ctor, param.getArguments(element, context));
        } catch (PasreReflectionException e) {
            throw new XmlParseException(e);
        }
        NamedNodeMap nodes = element.getAttributes();
        for (int i = 0, n = nodes.getLength(); i < n; ++i) {
            Attr attr = (Attr) nodes.item(i);
            if (context.isHandled(attr)) continue;
            AttributeHandler<T> handler = attrHandlers.get(attr.getName());
            if (handler == null) {
                throw new XmlParseException(
                        "unknown attribute: " + attr, element);
            }
            handler.process(entity, attr, context);
        }
        return entity;
    }

    private MemberHandler<T> findHandlerByType(Class<?> type) {
    	if (type == null) return null;
    	
        LinkedList<Class<?>> stack = ToolKit.newLinkedList();
        stack.push(type);

        while (!stack.isEmpty()) {
            type = stack.pop();
            MemberHandler<T> handler = byTypeHandlers.get(type);
            if (handler != null) return handler;
            Class<?> parent = type.getSuperclass();
            if (parent != null) stack.push(parent);
            for (Class<?> iface : type.getInterfaces()) {
                stack.push(iface);
            }
        }
        return null;
    }

    public void processMember(
            T component, Element member, ParseContext context)
            throws XmlParseException {
        String tagName = member.getTagName();
        MemberHandler<T> handler = byNameHandlers.get(tagName);
        if (handler == null) {
            Class<?> type = context.getParsedType(tagName);
            handler = findHandlerByType(type);
            if (handler == null) {
                throw new XmlParseException(
                        "cannot find handler: " + tagName, member);
            }
            handler.processByType(component, member, context);
        } else {
            if (onceHandlers.containsKey(handler)) {
                if (!context.putMemberHandler(handler)) {
                    throw new XmlParseException(
                            "handler: " + handler + " has been executed",
                            member);
                }
            }
            handler.processByName(component, member, context);
        }
    }

    public String getTagName() {
        return tagName;
    }

    @Override
    public Class<? extends T> getType() {
        return type;
    }
}
